package main

import (
	"crypto/sha256"
	"encoding/binary"
	"encoding/hex"
	"fmt"
	"hash/crc32"
	"io"
	"log"
	"net"
	"net/http"
	"os"
	"os/signal"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/gosuri/uilive"
	"golang.org/x/text/language"
	"golang.org/x/text/message"
)

var endMagicBroadcastCh = make(chan bool, 1)
var endClientLoginCh = make(chan bool, 1)
var endInfoBroadcastCh = make(chan bool, 1)
var passCount = 0 //接收文件零丢包，且Hash核验通过
var failCount = 0 //接收文件有丢包，且Hash核验失败
/*
	type clientInfoStruct struct {
		ip         net.IP
		blockIndex uint32
	}
*/
var clientMutex = sync.Mutex{}
var clientIPs = make(map[string]uint32)
var clientInfoMutex = sync.Mutex{}
var clientInfo = make([]string, 0)
var lostSliceCount = 0
var sendLostSliceCount = 0

func server() {
	sigs := make(chan os.Signal, 1)
	signal.Notify(sigs, os.Interrupt)
	go func() {
		<-sigs
		fmt.Println("你按下了 Ctrl+C, 程序中断....")
		os.Exit(0)
	}()
	go httpServer()
	fmt.Println(now(), "开始广播服务器信息......")
	go sendMagic()
	time.Sleep(10 * time.Second)
	fmt.Printf("%s 找到 %d 个客户端\n", now(), len(clientIPs))
	if len(clientIPs) == 0 {
		fmt.Println("没有找到客户端，请检查网络连接是否正确，防火墙是否阻止了程序")
		os.Exit(-1)
	}
	fmt.Println(now(), "开始广播文件数据......")
	sendFile()
	fmt.Println("客户端丢失数据：", lostSliceCount, ", 发送丢失数据：", sendLostSliceCount)
	canClose := clientFileHashCheckStatus()
	if canClose {
		os.Exit(0)
	}

	infiniteLoop()
}

func handler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello, World!")
}

// 返回文件名
func handlerFileName(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "%s", fileName)
}

// 返回文件长度
func handlerFileLength(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "%d", fileLength)
}

// 返回文件SHA256值
func handlerFileHash(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "%s", fileHash)
}

// 获取客户端IP，并记录
func handlerClientLogin(w http.ResponseWriter, r *http.Request) {
	clientIP, _, err := net.SplitHostPort(r.RemoteAddr)
	if err != nil {
		log.Println("IP 地址处理错误")
	}
	pos := strings.IndexByte(clientIP, '%')
	if pos != -1 {
		clientIP = clientIP[:pos]
	}
	fmt.Fprintf(w, "%s", "OK")
	clientMutex.Lock()
	_, ok := clientIPs[clientIP]
	if !ok {
		//fmt.Println(now(), "Find Client: ", clientIP)
		clientIPs[clientIP] = 0xFFFFFFFF
	}
	clientMutex.Unlock()
}

// 获取客户端文件Hash验证消息,以http://[ServerIP]:[port]/verify?status=xxx方式调用
func handlerClientVerify(w http.ResponseWriter, r *http.Request) {
	/*
		clientIP, _, err := net.SplitHostPort(r.RemoteAddr)
		if err != nil {
			log.Println("IP address Parsing Error")
		}
		pos := strings.IndexByte(clientIP, '%')
		if pos != -1 {
			clientIP = clientIP[:pos]
		}
	*/
	statusString := r.URL.Query().Get("status")
	if statusString == "pass" {
		passCount++
	}
	if statusString == "fail" {
		failCount++
	}
	fmt.Fprintf(w, "%s", "OK")
}

// 返回块的SHA256值,应以http://[ServerIP]:[port]/blockhash?index=xxx方式调用
func handlerBlockHash(w http.ResponseWriter, r *http.Request) {
	indexString := r.URL.Query().Get("index")
	index, err := strconv.Atoi(indexString)
	if err != nil {
		http.Error(w, "错误的 Index", http.StatusBadRequest)
		return
	}
	if index >= len(blockHash) {
		fmt.Fprintf(w, "%s", "")
	} else {
		fmt.Fprintf(w, "%s", blockHash[index])
	}
}

// 返回slice的CRC32值,应以http://[ServerIP]:[port]/slicehash?index=xxx方式调用
func handlerSliceHash(w http.ResponseWriter, r *http.Request) {
	indexString := r.URL.Query().Get("index")
	index, err := strconv.Atoi(indexString)
	if err != nil {
		http.Error(w, "错误的 Index", http.StatusBadRequest)
		return
	}
	if index >= len(sliceHash) {
		fmt.Fprintf(w, "%s", "")
	} else {
		fmt.Fprintf(w, "%d", sliceHash[index])
	}
}

// 块传送过程中发送丢失数据,应以http://[ServerIP]:[port]/lostslice？inde=xxx方式调用
// 这里发送的是buffer缓冲区的数据, index最大值是sliceOfBlock
func handlerGetLostSlice(w http.ResponseWriter, r *http.Request) {
	lostSliceCount++
	indexString := r.URL.Query().Get("index")
	index, err := strconv.Atoi(indexString)
	if err != nil {
		http.Error(w, "错误的 Index", http.StatusBadRequest)
		return
	}
	if index >= int(sliceOfBlock) {
		http.Error(w, "超出 Index 范围", http.StatusBadRequest)
		return
	}
	bufferMutex.Lock()
	data := buffer[index*sliceLength : (index+1)*sliceLength]
	bufferMutex.Unlock()
	w.Header().Set("Content-Type", "application/octet-stream")
	w.Header().Set("Content-Length", strconv.Itoa(len(data)))
	// 将二进制数据写入ResponseWriter
	_, err = w.Write(data)
	if err != nil {
		http.Error(w, "返回数据错误：", http.StatusInternalServerError)
		return
	}
	sendLostSliceCount++
	fmt.Fprintf(w, "%s", "OK")
}

// 块传送发送后发送丢失数据,应以http://[ServerIP]:[port]/lostslice2?index=xxx方式调用
// 这里发送的是文件的数据, index最大值是totalSlice
func handlerGetLostSlice2(w http.ResponseWriter, r *http.Request) {

	indexString := r.URL.Query().Get("index")
	index, err := strconv.Atoi(indexString)
	if err != nil {
		http.Error(w, "错误的 Index", http.StatusBadRequest)
		return
	}
	if index >= int(totalSlice) {
		http.Error(w, "超出 Index 范围", http.StatusBadRequest)
		return
	}

}

func handlerFinishReceiveLostSlice(w http.ResponseWriter, r *http.Request) {
	clientIP, _, err := net.SplitHostPort(r.RemoteAddr)
	if err != nil {
		log.Println("IP 地址处理错误")
	}
	pos := strings.IndexByte(clientIP, '%')
	if pos != -1 {
		clientIP = clientIP[:pos]
	}
	clientInfoMutex.Lock()
	clientInfo = append(clientInfo, clientIP)
	clientInfoMutex.Unlock()
	fmt.Fprintf(w, "%s", "OK")
}

func httpServer() {
	// 注册处理器到路由
	http.HandleFunc("/", handler)
	http.HandleFunc("/filename", handlerFileName)
	http.HandleFunc("/filelength", handlerFileLength)
	http.HandleFunc("/filehash", handlerFileHash)
	http.HandleFunc("/login", handlerClientLogin)
	http.HandleFunc("/verify", handlerClientVerify)
	http.HandleFunc("/blockhash", handlerBlockHash)
	http.HandleFunc("/slicehash", handlerSliceHash)
	http.HandleFunc("/getlostslice", handlerGetLostSlice)
	http.HandleFunc("/finishreceivelostslice", handlerFinishReceiveLostSlice)
	http.HandleFunc("/getlostslice2", handlerGetLostSlice2)

	// 启动监听8080端口的HTTP服务器
	fmt.Println(now(), "启动 HTTP 服务, 端口:  "+serverWebPort+"...")
	err := http.ListenAndServe("[::]:"+serverWebPort, nil)
	if err != nil {
		panic(err)
	}
}

// 把文件读入block，然后依次发送，
// 每个block再分成若干slice多播出去，
// 发送block前发送开始消息，
// 发送block后发送结束消息，
// 并接收客户端完成的消息
func sendFile() error {
	endMagicBroadcastCh <- true
	endClientLoginCh <- true
	endInfoBroadcastCh <- true

	printer := message.NewPrinter(language.Chinese)
	fileLengthString := printer.Sprintf("%d", fileLength)
	dst := &net.UDPAddr{IP: broadcastGroup, Port: broadcastFilePort}
	writer := uilive.New()
	writer.Start()
	//侦听适当的服务端口
	c, err := net.ListenPacket("udp6", "[::]:0")
	if err != nil {
		log.Println("连接错误: ", err)
		return err
	}
	defer c.Close()

	data := make([]byte, sliceLength+16) //开始部分增加4字节标识，4字节序号，4字节长度，4字节CRC32校验

	file, err := os.Open(filePath)
	if err != nil {
		log.Println("打开文件错误: ", err)
		return err
	}
	defer file.Close()

	// 创建一个新的CRC32校验和计算器
	crc := crc32.New(crc32.MakeTable(crc32.IEEE))
	sha256Hash := sha256.New()
	sha256Hash2 := sha256.New()
	//传输数据
	var sliceIndex uint32 = 0
	var blockIndex uint32 = 0
	var totalSend int64 = 0
	//由于在win下，非server系统延时误差在15ms左右，以下调节延时接近15ms
	sleepOfSlice := 14000 * networkSpeed / sliceLength / 12
	fmt.Println("当前网速: ", networkSpeed, " Mbps")
	packetDelay := float64(8*sleepOfSlice*sliceLength) / float64(networkSpeed*1000)
	if packetDelay < 14 {
		packetDelay = 14
	}
	fmt.Printf("根据网速计算出的每 %d 数据包延迟 %.2f ms\n", sleepOfSlice, packetDelay)
	for {
		//读入大缓存中
		n, err := file.Read(buffer) // 读取文件内容到切片缓冲中
		if err != nil {
			if err == io.EOF {
				fileHash = hex.EncodeToString(sha256Hash.Sum(nil))
				endFile(c, dst)
				c.WriteTo(data[:0], dst) //发送个空包，防止客户端阻塞
				fmt.Println(now(), "文件数据传送结束")
				break
			} else {
				log.Println("读取文件错误: ", err)
				return err
			}
		}
		//块传送开始
		sha256Hash2.Reset()
		go sha256Hash.Write(buffer[:n])
		sha256Hash2.Write(buffer[:n])
		blockHashString := hex.EncodeToString(sha256Hash2.Sum(nil))
		blockHash = append(blockHash, blockHashString)

		beginBlock(c, dst, blockIndex, uint32(n))
		var s int = 0
		var clock int = 0
		//划分成小块发出
		for {
			var l int = sliceLength
			if (n - s) < sliceLength {
				l = n - s
			}

			copy(data[:4], []byte(dataString))
			binary.LittleEndian.PutUint32(data[4:8], sliceIndex) //填充索引
			binary.LittleEndian.PutUint32(data[8:12], uint32(l)) //填充长度

			// 计算数据块的CRC32值
			crc.Reset()
			crc.Write(buffer[s : s+l])
			crcValue := crc.Sum32()
			sliceHash = append(sliceHash, crcValue)

			binary.LittleEndian.PutUint32(data[12:16], crcValue) //填充crc32校验值
			copy(data[16:l+16], buffer[s:s+l])                   //填充数据
			if _, err := c.WriteTo(data[:l+16], dst); err != nil {
				fmt.Println(now(), "多播数据错误: ", err)
				return err
			}
			// 睡眠一定时间
			clock++
			if clock == sleepOfSlice {
				time.Sleep(time.Duration(packetDelay) * time.Millisecond)
				clock = 0
			}

			sliceIndex++
			s = s + l
			if s == n {
				if clock%sleepOfSlice > 0 {
					time.Sleep(time.Duration(packetDelay) * time.Millisecond)
				}
				break
			}

		}
		//向客户端发送块传送结束标记
		endBlock(c, dst)
		totalSend += int64(n)
		totalSendString := printer.Sprintf("%d", totalSend)
		fmt.Fprintln(writer, now(), "多播进度: ", totalSendString, "/", fileLengthString)
		blockIndex++
	}

	writer.Stop()
	return nil
}

func beginBlock(c net.PacketConn, dst *net.UDPAddr, blockIndex uint32, blockLength uint32) error {
	data := make([]byte, 16)
	copy(data[:4], []byte(infoString))
	copy(data[4:8], []byte(beginBlockString))
	binary.LittleEndian.PutUint32(data[8:12], blockIndex)
	binary.LittleEndian.PutUint32(data[12:16], blockLength)
	for i := 0; i < ControlPacketRepeat; i++ {
		_, err := c.WriteTo(data[:16], dst)
		if err != nil {
			return err
		}
		time.Sleep(30 * time.Millisecond)
	}
	return nil
}

func endBlock(c net.PacketConn, dst *net.UDPAddr) error {
	endBlockChan := make(chan bool, 1)
	clientInfo = make([]string, 0)
	data := make([]byte, 16)
	copy(data[:4], []byte(infoString))
	copy(data[4:8], []byte(endBlockString))
	go func() {
		start := time.Now()
		for {
			if len(clientInfo) == len(clientIPs) {
				endBlockChan <- true
				break
			}
			time.Sleep(15 * time.Millisecond)
			//超时 1s 后不等待继续发送数据
			if time.Since(start) >= time.Second {
				endBlockChan <- true
				break
			}
		}
	}()
	for i := 0; i < ControlPacketRepeat; i++ {
		_, err := c.WriteTo(data[:16], dst)
		if err != nil {
			return err
		}
		time.Sleep(30 * time.Millisecond)
	}
	<-endBlockChan
	return nil
}

func endFile(c net.PacketConn, dst *net.UDPAddr) error {
	data := make([]byte, 16)
	copy(data[:4], []byte(infoString))
	copy(data[4:8], []byte(endFileString))
	for i := 0; i < ControlPacketRepeat; i++ {
		_, err := c.WriteTo(data[:16], dst)
		if err != nil {
			return err
		}
		time.Sleep(30 * time.Millisecond)
	}
	return nil
}

// 多播发送服务端识别信息
func sendMagic() error {
	dst := &net.UDPAddr{IP: broadcastGroup, Port: broadcastMagicPort}

	c, err := net.ListenPacket("udp6", "[::]:0")
	if err != nil {
		fmt.Println("连接错误: ", err)
		return err
	}
	defer c.Close()

	data := []byte(magicNumber)
	for {
		_, err := c.WriteTo(data, dst)
		if err != nil {
			fmt.Println("多播服务器 IP 地址错误: ", err)
			return err
		}

		time.Sleep(50 * time.Millisecond)
		if len(endMagicBroadcastCh) > 0 {
			endMagicBroadcastFlag := <-endMagicBroadcastCh
			if endMagicBroadcastFlag {
				fmt.Println(now(), "结束多播服务器信息")
				break
			}
		}
	}

	return nil
}

func clientFileHashCheckStatus() bool {
	var count = len(clientIPs)
	if count == 0 {
		return true
	}
	var delay = 0
	var c = 0
	for {
		c = passCount + failCount
		if c == count {
			break
		}
		time.Sleep(100 * time.Millisecond)
		if c > 0 {
			delay++
			if delay > 100 { //第一个验证结束的主机延迟10秒后
				break
			}
		}
	}
	fmt.Println(now(), "收到: ", c, "; Hash 正确: ", passCount, "; Hash 错误: ", failCount)
	return failCount == 0
}
